/*
 * Copyright 2002-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.fjsei.yewu.index;

import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.query_dsl.DateRangeQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery;
import com.querydsl.core.BooleanBuilder;
import graphql.relay.Connection;
import graphql.schema.DataFetchingEnvironment;
import md.cm.base.Companies;
import md.cm.unit.Unit;
import md.specialEqp.ReportRepository;
import md.specialEqp.inspect.*;
import md.specialEqp.type.PipingUnitCommon;
import md.system.User;
//import org.elasticsearch.index.query.BoolQueryBuilder;
//import org.elasticsearch.index.query.QueryBuilders;
//import org.elasticsearch.index.query.RangeQueryBuilder;
import org.fjsei.yewu.aop.MetricsLogger;
import org.fjsei.yewu.graphql.DbPageConnection;
import org.fjsei.yewu.graphql.IdMapper;
import org.fjsei.yewu.input.IspCommonInput;
import org.fjsei.yewu.input.NewsEsInput;
import org.fjsei.yewu.input.PipingUnitInput;
import org.fjsei.yewu.jpa.PageOffsetFirst;
import org.fjsei.yewu.resolver.Comngrql;
import org.fjsei.yewu.util.Tool;
//import org.hibernate.search.backend.elasticsearch.lowlevel.query.impl.Queries;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.client.elc.NativeQueryBuilder;
//import org.springframework.data.elasticsearch.client.elc.QueryBuilders;
//import org.springframework.data.elasticsearch.client.erhlc.NativeSearchQuery;
//import org.springframework.data.elasticsearch.client.erhlc.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
//import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHitSupport;
import org.springframework.data.elasticsearch.core.SearchHits;
//import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
//import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
//import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.execution.BatchLoaderRegistry;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

//import static org.elasticsearch.index.query.QueryBuilders.matchPhraseQuery;
//import static org.elasticsearch.index.query.QueryBuilders.termQuery;

/**默认Unit模型的属性； graphQL接口;
 * 底下批处理方案还不是一种的。 注释掉的也是1个方案。
 * 每一个graphQL的Object对象type都需单独声明一个XxxController implements IdMapper<type> {} 否则Id都无法转换成Relay要的GlobalID的。
 * */

@Controller
public class PipingUnitEsController extends Comngrql implements IdMapper<PipingUnitEs> {
	@PersistenceContext(unitName = "entityManagerFactorySei")
	private EntityManager emSei;
	//private final Units units;
	private final Companies companies;
	private final TaskRepository tasks;
	private final IspRepository isps;
	//private final ApprovalStmRepository  approvalStms;
	private final ReportRepository  reports;
	private final DetailRepository  details;
	@Autowired
	private ElasticsearchOperations esTemplate;

	/** registry参数：IDEA报红却能运行成功，实际运行注入DefaultBatchLoaderRegistry;
     *  消除报红，测试平台Units没有报红 https://blog.csdn.net/zy103118/article/details/84523452
	 * 添加了 @ComponentScan({"md","org.fjsei.yewu"}) 以后: Units报红消除了。
	 * */
	public PipingUnitEsController(BatchLoaderRegistry registry, Companies companies, TaskRepository tasks, IspRepository isps, ReportRepository reports, DetailRepository details) {
		this.companies = companies;
		this.tasks = tasks;
		this.isps = isps;
		this.reports = reports;
		this.details = details;
	}

	/**当前我的检验业务： 不上ES搜索，查关系数据库，又没搞对索引：太慢了！
	 * 【综合考量应该怎么加索引】还没规划设置好索引，本查询特别的慢！很多查询语句也并不是都需要那么多的字段，Entity Projection没用上。不同语句使用的频率，权衡代价和获益。
	 * */
//	@QueryMapping
	public Connection<Detail> findMeDetails(@Argument String orderBy, @Argument Boolean asc, @Argument IspCommonInput where, @Argument Integer first,
											@Argument String after, @Argument Integer last, @Argument String before, DataFetchingEnvironment env) {
		User user= checkAuth();
		if(user==null)   return null;
		DbPageConnection<Detail> connection=new DbPageConnection(env);
		int offset=connection.getOffset();
		int limit=connection.getLimit();
		Pageable pageable;
		if (!StringUtils.hasLength(orderBy))
			pageable = PageOffsetFirst.of(offset, limit);
		else
			pageable = PageOffsetFirst.of(offset, limit, Sort.by(asc ? Sort.Direction.ASC : Sort.Direction.DESC, orderBy));
		QDetail qm = QDetail.detail;
		BooleanBuilder builder = new BooleanBuilder();
		BooleanBuilder userbuild = new BooleanBuilder();
		//默认：在这个位置qm.isp.report.stm就会出现null 空指针(IDEA观察到)，但可能也根本就没提示的  直接歇菜。querydsl解决办法@QueryInit("report.stm.master")多对一一对一的字段才会出现此类问题。
		userbuild.or(qm.isp.report.stm.master.eq(user)).or(qm.isp.report.stm.authr.contains(user));
		BooleanBuilder stabuild = new BooleanBuilder();
		stabuild.or(qm.isp.report.stm.sta.eq(Procedure_Enum.BEGIN)).or(qm.isp.report.stm.sta.eq(Procedure_Enum.MAKE));
		builder.and(userbuild).and(stabuild);
		if(StringUtils.hasText(where.getServu())) {
			Unit unit=fromInputUnitGlobalID(where.getServu());
			Assert.notNull(unit,"未找到Unit:"+where.getServu());
			builder.and(qm.task.servu.id.eq(unit.getId()));
		}
		if(StringUtils.hasText(where.getCod()))
			builder.and(qm.isp.dev.cod.containsIgnoreCase(where.getCod()));
		if(StringUtils.hasText(where.getOid()))
			builder.and(qm.isp.dev.oid.containsIgnoreCase(where.getOid()));
		if(StringUtils.hasText(where.getIdent()))
			builder.and(qm.ident.containsIgnoreCase(where.getIdent()));
		if(StringUtils.hasText(where.getNo()))
			builder.and(qm.isp.no.containsIgnoreCase(where.getNo()));
		//【神奇了 querydsl】实际上这builder在这直接照抄findMeIsps(){}里面的竟然也可以！ 会从QIsp QDetail自动生成关联关系的。我都没定义QDetail的必要了。
		Slice<Detail> rpage= (Slice<Detail>)details.findAll(builder,pageable);
		List<Detail> isps=(List<Detail>) rpage.toList();
		return connection.setListData(isps).get(env);
	}

	/**管道单元,ES搜索引擎利用
	 * */
	@MetricsLogger
	@QueryMapping
	@PreAuthorize("hasAnyRole('JyUser','Master')")
	public Connection<PipingUnitCommon> searchPipingUnitEs(@Argument PipingUnitInput where, @Argument String after, @Argument Integer first,
														   @Argument String orderBy, @Argument Boolean asc, DataFetchingEnvironment env) {
		DbPageConnection<PipingUnitCommon> connection=new DbPageConnection(env);
		int offset=connection.getOffset();
		int limit=connection.getLimit();
		Pageable pageable;
		if (!StringUtils.hasText(orderBy))
			pageable = PageOffsetFirst.of(offset, limit);
		else
			pageable = PageOffsetFirst.of(offset, limit, Sort.by(asc ? Sort.Direction.ASC : Sort.Direction.DESC, orderBy));

		List<Query> listquerys = new ArrayList<Query>();
		if (StringUtils.hasText(where.getCode())) {
//			boolQueryBuilder.must(matchPhraseQuery("code", where.getCode()).slop(9));   //因pcode_tokenizer增强后最少1个字符能查出
			listquerys.add(Query.of(q -> q
					.matchPhrase(m -> m
							.field("code")
							.query(where.getCode()).slop(9)
					)
			));
		}
		if (StringUtils.hasText(where.getRno()))
			listquerys.add(Query.of(q -> q
					.term(m -> m
							.field("rno").value(where.getRno())
					)
			));
		//{		boolQueryBuilder.must(termQuery("rno",where.getRno())); 	}
//		if (StringUtils.hasText(where.getUseu())) {
//			//前端【单位选择】无法直接选定返回Unit.id字段，只能在后端主动把Company货Person转查关联出Unit ID;首先单位必须定位:Person的一定是某个个人。
//			Unit unit=fromInputUnitGlobalID(where.getUseu());
//			boolQueryBuilder.must(termQuery("useu.id", unit.getId().toString()));
//		}
		if (StringUtils.hasText(where.getUseu())) {
			Unit unit=fromInputUnitGlobalID(where.getUseu());
			listquerys.add(Query.of(q -> q
					.term(m -> m
							.field("pipe.useu.id").value(unit.getId().toString())
					)
			));
		}
		if (StringUtils.hasText(where.getStart()))
			listquerys.add(Query.of(q -> q
					.matchPhrase(m -> m
							.field("start")
							.query(where.getStart())
					)
			));
		if (StringUtils.hasText(where.getStop()))
			listquerys.add(Query.of(q -> q
					.matchPhrase(m -> m
							.field("stop")
							.query(where.getStop())
					)
			));
		if (null!=where.getUst())
			listquerys.add(Query.of(q -> q
					.term(m -> m
							.field("ust").value(where.getUst().name())
					)
			));
		if (null!=where.getReg())
			listquerys.add(Query.of(q -> q
					.term(m -> m
							.field("reg").value(where.getReg().name())
					)
			));
		if (StringUtils.hasText(where.getLevel()))
			listquerys.add(Query.of(q -> q
					.term(m -> m
							.field("level").value(where.getLevel())
					)
			));
		if (StringUtils.hasText(where.getPipe())) {
			Tool.ResolvedGuuid gId= Tool.fromGluuId(where.getPipe());
			//报错cannot write xcontent for unknown value of type class java.util.UUID
			listquerys.add(Query.of(q -> q
					.term(m -> m
							.field("pipe.id").value(gId.getId().toString())
					)
			));
		}
		if (null!=where.getNxtd21() || null!=where.getNxtd22()) {		//根据时间范围查询? null时间?
			listquerys.add(Query.of(q -> q.range(RangeQuery.of(rq -> rq.date(DateRangeQuery.of(c -> c
							.field("nxtd2")
							.from(null!=where.getNxtd21()? String.valueOf(where.getNxtd21()) : null)
							.to(null!=where.getNxtd22()? String.valueOf(where.getNxtd22()) : null)
					))
			))));
		}
		if (null!=where.getNxtd11() || null!=where.getNxtd12()) {
			listquerys.add(Query.of(q -> q.range(RangeQuery.of(rq -> rq.date(DateRangeQuery.of(c -> c
							.field("nxtd1")
							.from(null!=where.getNxtd11()? String.valueOf(where.getNxtd11()) : null)
							.to(null!=where.getNxtd12()? String.valueOf(where.getNxtd12()) : null)
					))
			))));
		}
		if (StringUtils.hasText(where.getUseuName())) {
			//不能用 boolQueryBuilder.must(matchPhraseQuery("pipe.useu.name",where.getUseuName()));
			listquerys.add(Query.of(q -> q
					.bool(p -> p
							.should(Query.of(c -> c
											.matchPhrase(m -> m
													.field("pipe.useu.company.name").query(where.getUseuName())
											)),
									Query.of(c -> c
											.matchPhrase(m -> m
													.field("pipe.useu.person.name").query(where.getUseuName())
											))
							)
					)
			));
		}
		if (null!=where.getBus()) {
			Tool.ResolvedGuuid gId= Tool.fromGluuId(where.getBus());
//			boolQueryBuilder.must(termQuery("dets.id", gId.getId().toString()));		//未测试dets[id1,2]
			listquerys.add(Query.of(q -> q
					.term(m -> m
							.field("dets.id").value(gId.getId().toString())
					)
			));
		}

		NativeQueryBuilder  nativeQueryBuilder = NativeQuery.builder()
//				.withQuery(q -> q.bool(			//改成过滤器模式的 速度快点，但是SearchHit返回的score=1.0的。
				.withFilter(q -> q.bool(
						v->v.must(listquerys)
					)
				);
		NativeQuery query =nativeQueryBuilder.withPageable(pageable).build();
		//另外一种模式的是： Criteria criteria = new Criteria("price").is(42.0);	Query query = new CriteriaQuery(criteria);更不好用！，还有第三种模式StringQuery的。
		SearchHits<PipingUnitEs> searchHits = esTemplate.search(query, PipingUnitEs.class);
		List<SearchHit<PipingUnitEs>> hits=searchHits.getSearchHits();
		Iterable<PipingUnitEs> list= (List<PipingUnitEs>) SearchHitSupport.unwrapSearchHits(hits);
		//这里加入Objects.requireNonNull(query.getQuery())，后端没报错，前端依然报出"null"；感觉很不好了，后端看不出报错来源。
//		String queryDslStr= Objects.requireNonNull(query.getQuery()).toString();
		String queryDslStr= Objects.toString(query.getFilter(),"空");		//改成过滤器模式的 query3.getQuery().toString();
		List<PipingUnitCommon>  elevators = new ArrayList<PipingUnitCommon>();
		list.forEach(item -> {
			elevators.add(item);
		});
		//后端反而不如前端严格，前端报错 Runtime Object type 'EqpEs' is not a possible type for 'PipingUnitCommon
		return connection.setListData(elevators).get(env);
	}


	/**管道单元,ES搜索引擎利用
	 * 这里的@Argument PipingUnitInput where,类型不一致不报错的，就是字段不同旧没有数据！
	 * */
	@MetricsLogger
	@QueryMapping
	@PreAuthorize("hasAnyRole('JyUser','Master')")
	public Connection<NewsEs> searchNewsEs(@Argument NewsEsInput where, @Argument String after, @Argument Integer first,
										   @Argument String orderBy, @Argument Boolean asc, DataFetchingEnvironment env) {
		DbPageConnection<NewsEs> connection=new DbPageConnection(env);
		int offset=connection.getOffset();
		int limit=connection.getLimit();
		Pageable pageable;
		if (!StringUtils.hasText(orderBy))
			pageable = PageOffsetFirst.of(offset, limit);
		else
			pageable = PageOffsetFirst.of(offset, limit, Sort.by(asc ? Sort.Direction.ASC : Sort.Direction.DESC, orderBy));

		List<Query> listquerys = new ArrayList<Query>();

		if (null!=where.getChn())
			listquerys.add(Query.of(q -> q
					.terms(m -> m
							.field("chn").terms( k->k
									.value( where.getChn().stream().map(FieldValue::of).collect(Collectors.toList())) )
							)
					)
			);

		if (StringUtils.hasText(where.getTitle()))
			listquerys.add(Query.of(q -> q
					.matchPhrase(m -> m
							.field("title")
							.query(where.getTitle())
					)
			));

		if (null!=where.getPdate())
			listquerys.add(Query.of(q -> q
					.term(m -> m
							.field("pdate").value(String.valueOf(where.getPdate()))
					)
			));

		NativeQueryBuilder  nativeQueryBuilder = NativeQuery.builder()
//				.withQuery(q -> q.bool(			//改成过滤器模式的 速度快点，但是SearchHit返回的score=1.0的。
				.withFilter(q -> q.bool(
								v->v.must(listquerys)
						)
				);
		NativeQuery query =nativeQueryBuilder.withPageable(pageable).build();
		//另外一种模式的是： Criteria criteria = new Criteria("price").is(42.0);	Query query = new CriteriaQuery(criteria);更不好用！，还有第三种模式StringQuery的。
		SearchHits<NewsEs> searchHits = esTemplate.search(query, NewsEs.class);
		List<SearchHit<NewsEs>> hits=searchHits.getSearchHits();
		Iterable<NewsEs> list= (List<NewsEs>) SearchHitSupport.unwrapSearchHits(hits);
		//这里加入Objects.requireNonNull(query.getQuery())，后端没报错，前端依然报出"null"；感觉很不好了，后端看不出报错来源。
//		String queryDslStr= Objects.requireNonNull(query.getQuery()).toString();
		String queryDslStr= Objects.toString(query.getFilter(),"空");		//改成过滤器模式的 query3.getQuery().toString();
		List<NewsEs>  listEnt = new ArrayList<NewsEs>();
		list.forEach(item -> {
			listEnt.add(item);
		});
		//后端反而不如前端严格，前端报错 Runtime Object type 'EqpEs' is not a possible type for 'PipingUnitCommon
		return connection.setListData(listEnt).get(env);
	}

}



/*版本升级 NativeSearchQueryBuilder 被淘汰。 【注意】 Query 容易同名太多，很可能搞错限定包的全路径类名称。
co.elastic.clients.elasticsearch._types.query_dsl.Query implementations from the Elasticsearch library therefore named "native".
又得修改咯；从spring data-elasticsearch 5.1版本开始 Queries文档  https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/
 另外一种模式的是： Criteria criteria = new Criteria("price").is(42.0);	Query query = new CriteriaQuery(criteria); 还有第三种模式StringQuery的。
		NativeQueryBuilder ntqBuilder= NativeQuery.builder()
				.withAggregation("lastNames", Aggregation.of(a -> a
						.terms(ta -> ta.field("last-name").size(10))))
				.withQuery(q -> q.match(m -> m
								.field("firstName").query("dddfgfirstName")
						)
				);
		ntqBuilder= ntqBuilder.withQuery();
if (StringUtils.hasText(where.getUseuName())) {
	List<Query> listqueryUsueN = new ArrayList<Query>();
	listqueryUsueN.add(Query.of(c -> c
		.matchPhrase(m -> m
		.field("pipe.useu.company.name").query(where.getUseuName())
		)));
	listqueryUsueN.add(Query.of(c -> c
		.matchPhrase(m -> m
		.field("pipe.useu.person.name").query(where.getUseuName())
		)));
  listQueryAll.add(Query.of(c -> c
		.bool(p -> p
		.should(listqueryUsueN)
		)));
}
Elasticsearch 之 Filter 与 Query 有啥不同？   https://zhuanlan.zhihu.com/p/164760305
filter 和 must_not 是不会影响算分的，通过查询结果中可以看到 _score 都是 0。
ES原生查询语法的；	 https://blog.csdn.net/winterking3/article/details/104015071
ES：Query DSL语法	 https://blog.csdn.net/qq_41091684/article/details/127769515
Elasticsearch 窗口查询三种方式:1, Page分页 max_result_window默认为10000; @2 search_after 单方向游标; #3 scroll 大快照:局限当前连接有状态的才能使用;
另外模式import static org.springframework.data.elasticsearch.client.elc.Queries.termQuery;这个包实际就是简单的扩展，把函数式改成普通的。?感觉意义不大。
* */
